package triangulation;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;

import util.GeoTriangle;
import util.GeoVertex;
import delaunay_triangulation.Delaunay_Triangulation;
import delaunay_triangulation.Point_dt;
import delaunay_triangulation.Triangle_dt;

public class DelaunayMapping
{
	public class VertexIndex
	{
		private HashSet<Double> dim_x;
		private HashSet<Double> dim_y;
		private HashSet<Double> dim_z;
		
		private HashMap<Double, HashMap<Double, HashMap<Double, Integer>>> indices; // actual indices
		private Integer index; // current index
		
		public VertexIndex()
		{
			dim_x = new HashSet<Double>();
			dim_y = new HashSet<Double>();
			dim_z = new HashSet<Double>();
			index = 0;
			indices = new HashMap<Double, HashMap<Double,HashMap<Double,Integer>>>();
		}
		
		public boolean add(double x, double y, double z)
		{
			boolean bContains_x = dim_x.contains(x);
			boolean bContains_y = dim_y.contains(y);
			boolean bContains_z = dim_z.contains(z);
			
			// if any dimension of the vertex is not within the hashset then we do not have the point yet!
			if (bContains_x == false || bContains_y == false || bContains_z == false)
			{
				dim_x.add(x);
				dim_y.add(y);
				dim_z.add(z);
				
				// we have the x component, get the y-mapping of the x component and see if we have a match
				if (bContains_x)
				{
					HashMap<Double, HashMap<Double, Integer>> ycomponents = indices.get(x);
					if (bContains_y) // we have a match, only component left is the z and it better be different
					{
						if (bContains_z) // duplicate point, ignore?
						{
//							HashMap<Double, Integer> zcomponents = ycomponents.get(y);
							return false;
						}
						else
						{
							HashMap<Double, Integer> zcomponents = new HashMap<Double, Integer>();
							zcomponents.put(z, index);
							index++;
							
							ycomponents.put(y, zcomponents);
						}
					}
					else
					{
						HashMap<Double, Integer> zcomponents = new HashMap<Double, Integer>();
						zcomponents.put(z, index);
						index++;
						
						ycomponents.put(y, zcomponents);
					}
				}
				else 
				{
					HashMap<Double, HashMap<Double, Integer>> ycomponents = new HashMap<Double, HashMap<Double,Integer>>();
					HashMap<Double, Integer> zcomponents = new HashMap<Double, Integer>();
					zcomponents.put(z, index);
					index++;
					
					ycomponents.put(y, zcomponents);
					indices.put(x, ycomponents);
				}
				
				return true;
			}
			
			return false;
		}
		
		public Integer get_index(double x, double y, double z)
		{
			boolean bContains_x = dim_x.contains(x);
			boolean bContains_y = dim_y.contains(y);
			boolean bContains_z = dim_z.contains(z);
			
			// if any dimension of the vertex is not within the hashset then we do not have the point yet!
			if (bContains_x && bContains_y && bContains_z) return indices.get(x).get(y).get(z);
			return null;
		}
		
		public void clear()
		{
			dim_x.clear();
			dim_y.clear();
			dim_z.clear();

			for (HashMap<Double, HashMap<Double, Integer>> y : indices.values())
			{
				for (HashMap<Double, Integer> z: y.values()) z.clear();
				y.clear();
			}
			indices.clear();
		}
	}
	
	
	public static String ConvertToStringKey(GeoVertex g) { return g.x()+"_"+g.y()+"_"+g.z(); }
	public static String ConvertToStringKey(Point_dt p) { return p.x()+"_"+p.y()+"_"+p.z(); }
	public static String ConvertToStringKey(Point_dt k0, Point_dt k1, Point_dt k2) { return ConvertToStringKey(k0)+","+ConvertToStringKey(k1)+","+ConvertToStringKey(k2);  }
	
	// contains string_key:index_key
	public HashMap<String, Integer> vertexIndices;
	public HashMap<String, Integer> triangleIndices;
//	public VertexIndex vertexIndices;
	
	public GeoVertex[] vertices;
	public GeoTriangle[] triangles;
	
	protected boolean bCreated;
	
	public GeoVertex getVertex(int id, int smoothness)
	{
		if (smoothness > 0) return vertices[id].getLOSVertex(smoothness-1);
		return vertices[id];
	}
	
	public DelaunayMapping()
	{
//		vertexIndices = new VertexIndex();
		vertexIndices = new HashMap<String, Integer>();
		triangleIndices = new HashMap<String, Integer>();
		vertices = null;
		triangles = null;
		bCreated = false;
	}
	
	public void clearMapping()
	{
		vertexIndices.clear();
		triangleIndices.clear();
		vertices = null;
		triangles = null;
		bCreated = false;
	}
	
	public void createMapping(Delaunay_Triangulation triangulation)
	{
		vertices = new GeoVertex[triangulation.size()];
		vertexIndices = new HashMap<String, Integer>();
		
		// since we do not know the exact number of triangles with 3 vertices within the trianglulation
		// as the Delaunay_Triangulation includes the border triangles (ie: two vertices + one going to the outer bounding box)
		// we use this wrapper array list and then use the toArray function to turn the list into an array
		ArrayList<GeoTriangle> triangleArrayWrapper = new ArrayList<GeoTriangle>();
		triangleIndices = new HashMap<String, Integer>();
		
		// create all the vertex mappings first
		int vertexIndex = 0;
		Iterator<Point_dt> iter_points = triangulation.verticesIterator();
		while (iter_points.hasNext())
		{
			Point_dt v = iter_points.next();
			GeoVertex gv = new GeoVertex(v);
			
			vertexIndices.put(ConvertToStringKey(v), vertexIndex);
			vertices[vertexIndex] = gv;
			gv.id = vertexIndex;
			vertexIndex++;
		}
		
		// now create all the triangle index mappings and vertex neighbour mappings
		int triangleIndex = 0;
		Iterator<Triangle_dt> iter_triangles = triangulation.trianglesIterator();
		while (iter_triangles.hasNext())
		{
			Triangle_dt dt = iter_triangles.next();
			Point_dt v0 = dt.p1();
			Point_dt v1 = dt.p2();
			Point_dt v2 = dt.p3();
			if (v0 != null && v1 != null && v2 != null)
			{
				String v0_key = ConvertToStringKey(v0);
				String v1_key = ConvertToStringKey(v1);
				String v2_key = ConvertToStringKey(v2);
				
				int v0_index = vertexIndices.get(v0_key);
				int v1_index = vertexIndices.get(v1_key);
				int v2_index = vertexIndices.get(v2_key);
				
				// create and set the vertex keys for the geo triangle
				GeoTriangle gt = new GeoTriangle();
				gt.vertexkeys[0] = v0_index;
				gt.vertexkeys[1] = v1_index;
				gt.vertexkeys[2] = v2_index;
				
				// now each of the vertices need to add a triangle reference
				vertices[v0_index].trianglekeys.add(triangleIndex);
				vertices[v1_index].trianglekeys.add(triangleIndex);
				vertices[v2_index].trianglekeys.add(triangleIndex);
				
				// now each of the vertices also needs to add the other vertices as its neighbours
				vertices[v0_index].neighbourkeys.add(v1_index);
				vertices[v0_index].neighbourkeys.add(v2_index);
				vertices[v1_index].neighbourkeys.add(v0_index);
				vertices[v1_index].neighbourkeys.add(v2_index);
				vertices[v2_index].neighbourkeys.add(v0_index);
				vertices[v2_index].neighbourkeys.add(v1_index);

				triangleIndices.put(ConvertToStringKey(v0, v1, v2), triangleIndex);
				triangleArrayWrapper.add(triangleIndex, gt);
				triangleIndex++;
			}
		}
		triangles = new GeoTriangle[triangleArrayWrapper.size()];
		triangleArrayWrapper.toArray(triangles);
		
		// now that each triangle has an index, we can fill in the triangle neighbour keys for each triangle
		
		bCreated = true;
	}

	
//	public void createMapping(Delaunay_Triangulation triangulation)
//	{
//		vertices = new GeoVertex[triangulation.size()];
//		vertexIndices = new VertexIndex();
//		
//		// since we do not know the exact number of triangles with 3 vertices within the trianglulation
//		// as the Delaunay_Triangulation includes the border triangles (ie: two vertices + one going to the outer bounding box)
//		// we use this wrapper array list and then use the toArray function to turn the list into an array
//		ArrayList<GeoTriangle> triangleArrayWrapper = new ArrayList<GeoTriangle>();
//		triangleIndices = new HashMap<String, Integer>();
//		
//		// create all the vertex mappings first
//		int vertexIndex = 0;
//		Iterator<Point_dt> iter_points = triangulation.verticesIterator();
//		while (iter_points.hasNext())
//		{
//			Point_dt v = iter_points.next();
//			GeoVertex gv = new GeoVertex(v);
//			
////			vertexIndices.put(ConvertToStringKey(v), vertexIndex);
//			
//			vertexIndices.add(v.x(), v.y(), v.z());
//			
//			vertices[vertexIndex] = gv;
//			gv.id = vertexIndex;
//			vertexIndex++;
//		}
//		
//		// now create all the triangle index mappings and vertex neighbour mappings
//		int triangleIndex = 0;
//		Iterator<Triangle_dt> iter_triangles = triangulation.trianglesIterator();
//		while (iter_triangles.hasNext())
//		{
//			Triangle_dt dt = iter_triangles.next();
//			Point_dt v0 = dt.p1();
//			Point_dt v1 = dt.p2();
//			Point_dt v2 = dt.p3();
//			if (v0 != null && v1 != null && v2 != null)
//			{
//				int v0_index = vertexIndices.get_index(v0.x(), v0.y(), v0.z());
//				int v1_index = vertexIndices.get_index(v1.x(), v1.y(), v1.z());
//				int v2_index = vertexIndices.get_index(v2.x(), v2.y(), v2.z());
//				
//				// create and set the vertex keys for the geo triangle
//				GeoTriangle gt = new GeoTriangle();
//				gt.vertexkeys[0] = v0_index;
//				gt.vertexkeys[1] = v1_index;
//				gt.vertexkeys[2] = v2_index;
//				
//				// now each of the vertices need to add a triangle reference
//				vertices[v0_index].trianglekeys.add(triangleIndex);
//				vertices[v1_index].trianglekeys.add(triangleIndex);
//				vertices[v2_index].trianglekeys.add(triangleIndex);
//				
//				// now each of the vertices also needs to add the other vertices as its neighbours
//				vertices[v0_index].neighbourkeys.add(v1_index);
//				vertices[v0_index].neighbourkeys.add(v2_index);
//				vertices[v1_index].neighbourkeys.add(v0_index);
//				vertices[v1_index].neighbourkeys.add(v2_index);
//				vertices[v2_index].neighbourkeys.add(v0_index);
//				vertices[v2_index].neighbourkeys.add(v1_index);
//
//				triangleIndices.put(ConvertToStringKey(v0, v1, v2), triangleIndex);
//				triangleArrayWrapper.add(triangleIndex, gt);
//				triangleIndex++;
//			}
//		}
//		triangles = new GeoTriangle[triangleArrayWrapper.size()];
//		triangleArrayWrapper.toArray(triangles);
//		
//		// now that each triangle has an index, we can fill in the triangle neighbour keys for each triangle
//		
//		bCreated = true;
//	}
	
}
